float random_size;
float2 g_screen_size;
float g_sample_rad;
float g_intensity;
float g_scale;
float g_bias;
float FarClip;

float4x4 InverseProjection;

float2 BlurDirection;

float2 TempBufferRes;

texture DepthBuffer;
sampler2D depthSampler = sampler_state
{
	Texture = <DepthBuffer>;
	MipFilter = NONE;
	MagFilter = POINT;
	MinFilter = POINT;
	AddressU = Clamp;
	AddressV = Clamp;
};

texture NormalBuffer;
sampler2D normalSampler = sampler_state
{
	Texture = <NormalBuffer>;
	MipFilter = NONE;
	MagFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Clamp;
	AddressV = Clamp;
};

texture RandomMap;
sampler2D randomSampler = sampler_state
{
	Texture = <RandomMap>;
	MipFilter = NONE;
	MagFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
};

texture SSAOBuffer;
sampler2D ssaoSampler = sampler_state
{
	Texture = <SSAOBuffer>;
	MipFilter = NONE;
	MagFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Clamp;
	AddressV = Clamp;
};

texture Texture;
sampler2D textureSampler = sampler_state
{
	Texture = <Texture>;
	MipFilter = NONE;
	MagFilter = LINEAR;
	MinFilter = LINEAR;
	AddressU = Clamp;
	AddressV = Clamp;
};

struct VS_INPUT
{
	float4 Position : POSITION0;
	float2 TexCoord : TEXCOORD0;
};

struct VS_OUTPUT
{
	float4 Position : POSITION0;
	float2 TexCoord : TEXCOORD0;
};

float3 getPosition(in float2 uv)
{
	float z = tex2D(depthSampler, uv).r;
	float x = uv.x * 2.0 - 1.0;
	float y = (1 - uv.y) * 2.0 - 1.0;
	float4 vpos = float4(x, y, z, 1.0f);
	vpos = mul(vpos, InverseProjection);
	return vpos / vpos.w;
}

float3 getNormal(in float2 uv)
{
	float4 normalData = tex2D(normalSampler,uv);

    //tranform normal back into [-1,1] range
    float3 normal = 2.0f * normalData.xyz - 1.0f;
	return normalize(normal);
}

float2 getRandom(in float2 uv)
{
	float2 randNormal = (tex2D(randomSampler, g_screen_size * uv / random_size).rg* 2.0 - 1.0);
	return randNormal;
}

float doAmbientOcclusion(in float2 tcoord,in float2 uv, in float3 p, in float3 cnorm)
{
	float3 diff = getPosition(tcoord + uv) - p;
	const float3 v = normalize(diff);
	const float d = length(diff)*g_scale;
	return max(0.0,dot(cnorm,v)-g_bias)*(1.0/(1.0+d))*g_intensity;
}

VS_OUTPUT VertexShaderFunction(VS_INPUT i)
{
    VS_OUTPUT o = (VS_OUTPUT)0;
	
	o.Position = i.Position;
	o.TexCoord = i.TexCoord;
	
	return o;
}

float4 PixelShaderFunction(VS_OUTPUT i) : COLOR0
{
	const float2 vec[4] = {float2(1,0),float2(-1,0),
							float2(0,1),float2(0,-1)};

	float3 p = getPosition(i.TexCoord);
	float3 n = getNormal(i.TexCoord);
	float2 rand = getRandom(i.TexCoord);

	float ao = 0.0f;
	float rad = g_sample_rad/p.z;

	//**SSAO Calculation**//
	int iterations = 4;
	[loop]
	for (int j = 0; j < iterations; ++j)
	{
		float2 coord1 = reflect(vec[j],rand)*rad;
		float2 coord2 = float2(coord1.x*0.707 - coord1.y*0.707,
								coord1.x*0.707 + coord1.y*0.707);
  
		ao += doAmbientOcclusion(i.TexCoord,coord1*0.25, p, n);
		ao += doAmbientOcclusion(i.TexCoord,coord2*0.5, p, n);
		ao += doAmbientOcclusion(i.TexCoord,coord1*0.75, p, n);
		ao += doAmbientOcclusion(i.TexCoord,coord2, p, n);
	}
	ao /= (float)iterations * 4.0;

	return 1 - ao;
}

VS_OUTPUT VertexShaderBlur(VS_INPUT input)
{
    VS_OUTPUT output = (VS_OUTPUT)0;
	
	output.Position = input.Position;
	output.TexCoord = input.TexCoord;
	return output;
}

float4 PixelShaderBlur(VS_OUTPUT input) : COLOR0
{
	float depth = tex2D(depthSampler,input.TexCoord).r;
	
	half divisor = 1;
	half occ   =  0;
	
	clip(-depth + 0.9999f);

	if (depth < 1)
	{
		occ  = tex2D(ssaoSampler, input.TexCoord).r;

		#define NUM_SAMPLERS 2
	
		for (int i=-NUM_SAMPLERS; i<= NUM_SAMPLERS; i++)
		{
			float2 uvOcc    = input.TexCoord + BlurDirection* (i);
			half newOcc = tex2D(ssaoSampler, uvOcc).r;
			float2 uvScreen = input.TexCoord + BlurDirection* (i);
			half newDepth =tex2D(depthSampler,uvScreen).r;
			half depthDif =  abs(depth - newDepth)*FarClip;
			half sz = max(0,0.2f - depthDif)*5;
			divisor += sz;
			occ+=newOcc*sz;
		}

		occ /= divisor;

	}
	return 1-occ;
}

VS_OUTPUT VertexShaderBlendFunction(float4 position : POSITION0, float2 texCoords : TEXCOORD0)
{
    VS_OUTPUT output;

    output.Position = position;
    output.TexCoord = texCoords;

    return output;
}

float4 PixelShaderBlendFunction(VS_OUTPUT input) : COLOR0
{
    return tex2D(textureSampler, input.TexCoord);
}

technique SSAO
{
	pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderFunction();
        PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}

technique SSAOBlur
{
	pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderBlur();
        PixelShader = compile ps_3_0 PixelShaderBlur();
    }
}

technique SSAOBlend
{
	pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderBlendFunction();
        PixelShader = compile ps_3_0 PixelShaderBlendFunction();
    }
}